const chalk = require('chalk')
const fs = require('fs')
/**
 * metalsmith: An extremely simple, pluggable static site generator.
 * pluggable: adj. 插接式连接，[电] 可插的
 */
const Metalsmith = require('metalsmith')
const Handlebars = require('handlebars')
const async = require('async')
/**
 * consolidate: Template engine consolidation library.
 * consolidation: 巩固、合并
 */
const render = require('consolidate').handlebars.render
const path = require('path')
/**
 * multimatch: Extends minimatch.match() with support for multiple patterns
 * multimatch(['unicorn', 'cake', 'rainbows'], ['*', '!cake']); //=> ['unicorn', 'rainbows']
 */
const multimatch = require('multimatch')
const getOptions = require('./options')
const ask = require('./ask')
const filter = require('./filter')
const logger = require('./logger')

// register handlebars helper
Handlebars.registerHelper('if_eq', function (a, b, opts) {
    return a === b
        ? opts.fn(this)
        : opts.inverse(this)
})

Handlebars.registerHelper('unless_eq', function (a, b, opts) {
    return a === b
        ? opts.inverse(this)
        : opts.fn(this)
})

/**
 * Generate a template given a `src` and `dest`.
 *
 * @param {String} name
 * @param {String} src
 * @param {String} dest
 * @param {Function} done
 */

module.exports = function generate (name, src, dest, done) {
    const opts = getOptions(name, src)
    // 要拷贝的项目源文件是模版代码所在仓库根目录下的template目录，请确保你的模板仓库根目录下含有template目录，否则会直接拉取仓库根目录
    const defaultTemplatePath = path.join(src, 'template')
    const isTemplatePathExists = fs.existsSync(defaultTemplatePath)
    const metalsmith = Metalsmith(isTemplatePathExists ? defaultTemplatePath : src)
    const data = Object.assign(metalsmith.metadata(), {
        destDirName: name,
        inPlace: dest === process.cwd(),
        noEscape: true
    })
    opts.helpers && Object.keys(opts.helpers).map(key => {
        Handlebars.registerHelper(key, opts.helpers[key])
    })

    const helpers = { chalk, logger }

    if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
        opts.metalsmith.before(metalsmith, opts, helpers)
    }

    metalsmith.use(askQuestions(opts.prompts))
        .use(filterFiles(opts.filters))
        .use(renderTemplateFiles(opts.skipInterpolation))

    if (typeof opts.metalsmith === 'function') {
        opts.metalsmith(metalsmith, opts, helpers)
    } else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
        opts.metalsmith.after(metalsmith, opts, helpers)
    }

    // #clean: Set whether to remove the destination directory before writing to it, or get the current setting. Defaults to true.
    metalsmith.clean(false)
    // start from template root instead of `./src` which is Metalsmith's default for `source`
        .source('.')
        // #destination(path): Set the relative path to the destination directory, or get the full one if no path is provided. The destination directory defaults to ./build.
        .destination(dest)
        // #build(fn): Build with the given settings and a callback having signature fn(err, files).
        .build((err, files) => {
            done(err)
            if (typeof opts.complete === 'function') {
                const helpers = { chalk, logger, files, fs }
                opts.complete(data, helpers)
            } else {
                logMessage(opts.completeMessage, data)
            }
        })

    return data
}

/**
 * Create a middleware for asking questions.
 *
 * @param {Object} prompts
 * @return {Function}
 */

function askQuestions (prompts) {
    return (files, metalsmith, done) => {
        ask(prompts, metalsmith.metadata(), done)
    }
}

/**
 * Create a middleware for filtering files.
 *
 * @param {Object} filters
 * @return {Function}
 */

function filterFiles (filters) {
    return (files, metalsmith, done) => {
        filter(files, filters, metalsmith.metadata(), done)
    }
}

/**
 * Template in place plugin.
 *
 * @param {Object} files
 * @param {Metalsmith} metalsmith
 * @param {Function} done
 */

function renderTemplateFiles (skipInterpolation) {
    // interpolation: 插入、篡改
    skipInterpolation = typeof skipInterpolation === 'string'
        ? [skipInterpolation]
        : skipInterpolation
    return (files, metalsmith, done) => {
        const keys = Object.keys(files)
        const metalsmithMetadata = metalsmith.metadata()
        async.each(keys, (file, next) => {
            // skipping files with skipInterpolation option
            if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) {
                return next()
            }
            const str = files[file].contents.toString()
            // do not attempt to render files that do not have mustaches
            if (!/{{([^{}]+)}}/g.test(str)) {
                return next()
            }
            render(str, metalsmithMetadata, (err, res) => {
                if (err) {
                    err.message = `[${file}] ${err.message}`
                    return next(err)
                }
                files[file].contents = new Buffer(res)
                next()
            })
        }, done)
    }
}

/**
 * Display template complete message.
 *
 * @param {String} message
 * @param {Object} data
 */

function logMessage (message, data) {
    if (!message) return
    render(message, data, (err, res) => {
        if (err) {
            console.error('\n   Error when rendering template complete message: ' + err.message.trim())
        } else {
            console.log('\n' + res.split(/\r?\n/g).map(line => '   ' + line).join('\n'))
        }
    })
}
